using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq.Expressions;
using System.Linq;
using System.Xml.Linq;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Utilities;

namespace SharePoint.DataMapper
{
	public enum LogicalOperator
	{
		Eq,
		Neq,
		Lt,
		Leq,
		Gt,
		Geq,
		IsNull,
		IsNotNull,
		And,
		Or,
		BeginsWith,
		Contains
	}

	public enum CamlExpressionType
	{
		View = 7000,
		Query,
		Where,
		Logical,
		OrderBy,
		FieldRef,
		Value,
		ViewFields,
		RowLimit,
		Lists,
		List,
		Webs,
		WithIndex,
	}

	public enum WebsScope
	{
		/// <summary>
		/// The query considers all Web sites that are descended from the current SPWeb object.
		/// </summary>
		Recursive,
		/// <summary>
		/// The query considers all Web sites that are in the same site collection as the current Web site.
		/// </summary>
		SiteCollection
	}

	public enum ViewScope
	{
		FilesOnly,
		Recursive,
		RecursiveAll,
	}

	public abstract class CamlExpression : Expression
	{
		public new CamlExpressionType NodeType
		{
			get { return (CamlExpressionType)base.NodeType; }
		}

		public CamlExpression(CamlExpressionType nodeType, Type type)
			: base((ExpressionType)nodeType, type)
		{
		}

		public virtual XElement BuildElement()
		{
			return new XElement(NodeType.ToString());
		}

		public override string ToString()
		{
			return BuildElement().ToString();
		}
	}

	public abstract class FieldExpression : CamlExpression
	{
		public string ValueType { get; private set; }

		public FieldExpression(CamlExpressionType nodeType, Type type, string valueType)
			: base(nodeType, type)
		{
			ValueType = valueType;
		}
	}

	public class FieldRefExpression : FieldExpression
	{
		public string Name { get; private set; }
		public bool Ascending { get; set; }
		public bool LookupId { get; set; }

		public FieldRefExpression(Type type, string name, string valueType)
			: this(type, name, valueType, true)
		{
		}

		public FieldRefExpression(Type type, string name, string valueType, bool ascending)
			: base(CamlExpressionType.FieldRef, type, valueType)
		{
			Name = name;
			Ascending = ascending;
		}

		public override XElement BuildElement()
		{
			XElement element = new XElement("FieldRef",
			                                new XAttribute("Name", Name),
			                                new XAttribute("Ascending", Ascending.ToString().ToUpper()),
			                                new XAttribute("LookupId", LookupId.ToString().ToUpper()));
			return element;
		}
	}

	public class ValueExpression : FieldExpression
	{
		public object Value { get; private set; }

		public ValueExpression(object value, string valueType)
			: this(value.GetType(), value, valueType)
		{
		}

		public ValueExpression(Type type, object value, string valueType)
			: base(CamlExpressionType.Value, type, valueType)
		{
			Value = value;
		}

		public override XElement BuildElement()
		{
			XElement element = new XElement("Value", new XAttribute("Type", ValueType));
			if (Value is DateTime)
			{
				element.Add(SPUtility.CreateISO8601DateTimeFromSystemDateTime((DateTime)Value));
			}
			else
			{
				element.Add(Value);
			}
			return element;
		}
	}

	public class LogicalExpression : CamlExpression
	{
		public LogicalOperator Operator { get; private set; }
		public CamlExpression LeftOperand { get; private set; }
		public CamlExpression RightOperand { get; private set; }

		public LogicalExpression(LogicalOperator @operator, CamlExpression leftOperand, CamlExpression rightOperand)
			: base(CamlExpressionType.Logical, typeof(bool))
		{
			Operator = @operator;
			LeftOperand = leftOperand;
			RightOperand = rightOperand;
		}

		public LogicalExpression(LogicalOperator @operator, CamlExpression leftOperand)
			: base(CamlExpressionType.Logical, typeof(bool))
		{
			Operator = @operator;
			LeftOperand = leftOperand;
		}

		public override XElement BuildElement()
		{
			XElement element = new XElement(Operator.ToString(), LeftOperand.BuildElement());
			if (RightOperand != null)
			{
				element.Add(RightOperand.BuildElement());
			}
			return element;
		}
	}

	public class QueryExpression : CamlExpression
	{
		public WhereExpression Where { get; set; }
		public OrderByExpression OrderBy { get; set; }

		public QueryExpression(Type type, WhereExpression whereExpression, OrderByExpression orderByExpression)
			: base(CamlExpressionType.Query, type)
		{
			Where = whereExpression;
			OrderBy = orderByExpression;
		}

		public override XElement BuildElement()
		{
			XElement element = new XElement("Query");
			if (Where != null)
			{
				element.Add(Where.BuildElement());
			}
			if (OrderBy != null)
			{
				element.Add(OrderBy.BuildElement());
			}
			return element;
		}
	}

	public class WhereExpression : CamlExpression
	{
		public LogicalExpression Logical { get; private set; }

		public WhereExpression(Type type, LogicalExpression expression)
			: base(CamlExpressionType.Where, type)
		{
			Logical = expression;
		}

		public override XElement BuildElement()
		{
			XElement element = new XElement("Where", Logical.BuildElement());
			return element;
		}
	}

	public class OrderByExpression : CamlExpression
	{
		public List<FieldRefExpression> Fields { get; private set; }

		public OrderByExpression(Type type, params FieldRefExpression[] fields)
			: this(type, fields.AsEnumerable())
		{
		}

		public OrderByExpression(Type type, IEnumerable<FieldRefExpression> fields)
			: base(CamlExpressionType.OrderBy, type)
		{
			Fields = new List<FieldRefExpression>(fields);
		}

		public override XElement BuildElement()
		{
			XElement element = new XElement("OrderBy");
			foreach (FieldRefExpression fieldRefExpression in Fields)
			{
				element.Add(fieldRefExpression.BuildElement());
			}
			return element;
		}
	}

	public class ViewFieldsExpression : CamlExpression
	{
		public ReadOnlyCollection<FieldRefExpression> Fields { get; private set; }

		public ViewFieldsExpression(params FieldRefExpression[] fields)
			: this(fields.AsEnumerable())
		{
		}

		public ViewFieldsExpression(IEnumerable<FieldRefExpression> fields)
			: base(CamlExpressionType.ViewFields, null)
		{
			Fields = fields.ToList().AsReadOnly();
		}

		public override XElement BuildElement()
		{
			XElement element = new XElement("ViewFields");
			foreach (FieldRefExpression fieldRefExpression in Fields)
			{
				element.Add(fieldRefExpression.BuildElement());
			}
			return element;
		}
	}

	public class RowLimitExpression : CamlExpression
	{
		public int RowLimit { get; private set; }

		public RowLimitExpression(int rowLimit)
			: base(CamlExpressionType.RowLimit, null)
		{
			RowLimit = rowLimit;
		}

		public override XElement BuildElement()
		{
			return new XElement("RowLimit", RowLimit);
		}
	}

	public class ViewExpression : CamlExpression
	{
		public ViewScope? Scope { get; set; }
		public QueryExpression Query { get; set; }
		public ViewFieldsExpression ViewFields { get; set; }
		public RowLimitExpression RowLimit { get; set; }
		public ListsExpression Lists { get; set; }
		public WebsExpression Webs { get; set; }

		public ViewExpression(Type type)
			: base(CamlExpressionType.View, type)
		{
		}

		public override XElement BuildElement()
		{
			XElement element = new XElement("View");
			if (Scope.HasValue)
			{
				element.Add(new XAttribute("Scope", Scope));
			}

			if (Query != null)
			{
				element.Add(Query.BuildElement());
			}
			if (ViewFields != null)
			{
				element.Add(ViewFields.BuildElement());
			}
			if (RowLimit != null)
			{
				element.Add(RowLimit.BuildElement());
			}
			if (Lists != null)
			{
				element.Add(Lists.BuildElement());
			}
			if (Webs != null)
			{
				element.Add(Webs.BuildElement());
			}
			return element;
		}
	}

	public class ListsExpression : CamlExpression
	{
		public int? ServerTemplate { get; set; }
		public int? BaseType { get; set; }
		public int? MaxListLimit { get; set; }
		public bool? Hidden { get; set; }

		public ReadOnlyCollection<ListExpression> Lists { get; private set; }

		public ListsExpression(params ListExpression[] lists)
			: this(lists.AsEnumerable())
		{
		}
		public ListsExpression(IEnumerable<ListExpression> lists)
			: base(CamlExpressionType.Lists, null)
		{
			Lists = lists.ToList().AsReadOnly();
		}

		public ListsExpression(int? baseType, int? serverTemplate)
			: this(baseType, serverTemplate, null)
		{
			ServerTemplate = serverTemplate;
			BaseType = baseType;
		}

		public ListsExpression(int? baseType, int? serverTemplate, int? maxListLimit)
			: base(CamlExpressionType.Lists, null)
		{
			ServerTemplate = serverTemplate;
			BaseType = baseType;
			MaxListLimit = maxListLimit;
		}

		public override XElement BuildElement()
		{
			XElement element = new XElement("Lists");
			if (ServerTemplate.HasValue)
			{
				element.Add(new XAttribute("ServerTemplate", ServerTemplate.Value));
			}
			if (BaseType.HasValue)
			{
				element.Add(new XAttribute("BaseType", BaseType.Value));
			}
			if (MaxListLimit.HasValue)
			{
				element.Add(new XAttribute("MaxListLimit", MaxListLimit.Value));
			}
			if (Lists != null)
			{
				foreach (ListExpression listExpression in Lists)
				{
					element.Add(listExpression.BuildElement());
				}
			}
			return element;
		}
	}

	public class WithIndexExpression : CamlExpression
	{
		public Guid FieldId { get; set; }
		public SPFieldType ValueType { get; set; }
		public string Value { get; set; }

		public WithIndexExpression(Guid fieldId, string value)
			: this(fieldId, value, SPFieldType.Text)
		{
		}

		public WithIndexExpression(Guid fieldId, string value, SPFieldType valueType)
			: base(CamlExpressionType.WithIndex, null)
		{
			FieldId = fieldId;
			Value = value;
			ValueType = valueType;
		}

		public override XElement BuildElement()
		{
			XElement element = new XElement("Lists");
			element.Add(new XAttribute("FieldId", FieldId.ToString("D")));
			element.Add(new XAttribute("Type", ValueType));
			element.Add(new XAttribute("Value", Value));
            
			return element;
		}
	}

	public class ListExpression : CamlExpression
	{
		public Guid Id { get; private set; }

		public ListExpression(Guid id)
			: base(CamlExpressionType.List, null)
		{
			Id = id;
		}

		public override XElement BuildElement()
		{
			return new XElement("List", new XAttribute("ID", Id));
		}
	}

	public class WebsExpression : CamlExpression
	{
		public WebsScope Scope { get; private set; }

		public WebsExpression(WebsScope scope)
			: base(CamlExpressionType.Webs, null)
		{
			Scope = scope;
		}

		public override XElement BuildElement()
		{
			return new XElement("Webs", new XAttribute("Scope", Scope));
		}
	}
}